Ekulelu's Blog

iOS画图轨迹圆滑处理

用户在画图过程中,系统从屏幕上获取到的是离散的点。而且这个点的获取是有一个最高频率的,当用户在屏幕上滑动速度很快的时候,获取到的两个点就会距离较远。

简单的绘制过程就是将相邻的两个点连接起来。如下图。但是正如上面所说的,如果用户滑动的速度太快,那么两个点的距离就会比较远,当用户画曲线的时候,就会出现很明显的转折角。

为了解决这个问题,我们需要借助于二次贝塞尔曲线。二次贝塞尔曲线的公式如下。当t为0的时候,B(t)的值是P0,当t为1的时候,B(t)的值是P2。P1是控制点,用来控制P0和P2之间的弯曲程度。

正如方程所显示的,我们需要三个点才能画出一段平滑的曲线。但是如果你直接将屏幕报上来的点,每三个一组去套用二次贝塞尔的公式,那么很遗憾,得出来的图形还是棱角分明的。正确地使用公式,我们需要一点姿势:
首先当有3个报点的时候,将相邻两个点的中点求出来,把他们作为P0和P2,然后把中间的那个报点作为P1控制点来套用公式。图像如下,其中蓝色的是报点,橙色的是中点。

当然这里有两处例外:第一个报点和第一个中点之间、最后一个报点和最后一个中点之间。因为这两处都不足3个点,这里有两种处理方法,一个是将这些段都不绘制;另外的一种是用绘制直线。个人比较偏向第二种做法,因为第一种做法在某些情况下会看到在我下手的地方没有绘制线段。

简单的实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)pan:(UIPanGestureRecognizer *)pan {
CGPoint currentPoint = [pan locationInView:self];
CGPoint midPoint = midpoint(previousPoint, currentPoint);
if (pan.state == UIGestureRecognizerStateBegan) {
[path moveToPoint:currentPoint];
} else if (pan.state == UIGestureRecognizerStateChanged) {
[path addQuadCurveToPoint:midPoint controlPoint:previousPoint];
}
previousPoint = currentPoint;
[self setNeedsDisplay];
}

这里说明一下为什么没有处理UIGestureRecognizerStateEnded事件。因为UIGestureRecognizerStateEnded事件里面报的点的坐标和UIGestureRecognizerStateChanged的最后一个报点的坐标是重合的。